/**
 * Created by ${PRODUCT_NAME}.
 * User: skylerparr
 * Date: 12/12/10
 * Time: 9:37 PM
 * To change this template use File | Settings | File Templates.
 */
package com.thoughtorigin.meta {
import com.thoughtorigin.meta.mock.MockMetaType;
import com.thoughtorigin.module.ModuleLoader;
import com.thoughtorigin.reflect.DescribeObjectMetaData;
import com.thoughtorigin.reflect.FunctionMetaDataItem;
import com.thoughtorigin.reflect.MetaDataItem;
import com.thoughtorigin.reflect.ObjectMetaData;
import com.thoughtorigin.reflect.metareadermocks.Sample1;

import flash.utils.Dictionary;
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;

import org.flexunit.asserts.assertEquals;
import org.flexunit.asserts.assertNotNull;
import org.flexunit.asserts.assertNull;
import org.flexunit.asserts.assertTrue;
import org.flexunit.asserts.fail;
import org.flexunit.async.Async;
import org.mockito.integrations.any;
import org.mockito.integrations.atLeast;
import org.mockito.integrations.given;
import org.mockito.integrations.inOrder;
import org.mockito.integrations.mock;
import org.mockito.integrations.useArgument;
import org.mockito.integrations.verify;

[RunWith("org.mockito.integrations.flexunit4.MockitoClassRunner")]
[Mock(type="com.thoughtorigin.reflect.DescribeObjectMetaData")]
[Mock(type="com.thoughtorigin.module.ModuleLoader")]
[Mock(type="com.thoughtorigin.meta.MetaOperator")]
public class FunctionMetaWorkerTest {

    private var _worker:FunctionMetaWorker;
    private var dict: Dictionary;

    [Before]
    public function setup():void {
        _worker = new FunctionMetaWorker();
        dict = new Dictionary();
        dict["sampleFunc"] = [];
    }

    [After]
    public function tearDown():void {
        _worker = null;
    }

    [Test]
    public function shouldAddMetaType():void {
        var m:MockMetaType = new MockMetaType();
        _worker.addMetaType("Mockey", m);
        assertNotNull(_worker.metaTypes["Mockey"]);
    }

    [Test]
    public function shouldIterateThroughAllMetaDataAndDelegateWorkToCorrectType():void {
        var callback:Function = function():void {
            assertEquals(m._object, sample);
            assertEquals(m._metadata, metadataItem);
            assertNotNull(m._callback);
            callbackCalled = true;
        }
        var callbackCalled:Boolean = false;

        var sample:Object = new Object();
        var metaData:ObjectMetaData = mock(DescribeObjectMetaData);
        assertNotNull(metaData);
        given(metaData.className).willReturn(getQualifiedClassName(Sample1));
        var metadataItem:MetaDataItem = new FunctionMetaDataItem("Mockey", [], "sampleFunc");
        dict["sampleFunc"].push(metadataItem);
        given(metaData.functionMetaData).willReturn(dict);

        var m:MockMetaType = new MockMetaType();
        _worker.addMetaType("Mockey", m);

        var moduleLoader: ModuleLoader = mock(ModuleLoader);
        _worker.moduleLoader = moduleLoader;
        given(moduleLoader.getClassByName(any())).willReturn(Object);

        var metaOperator: MetaOperator = mock(MetaOperator);
        _worker.metaOperator = metaOperator;
        given(metaOperator.inject(any(), any(), any())).will(useArgument(2).asFunctionAndCall(m));

        _worker.operate(sample, metaData, callback);

        if (!callbackCalled) {
            fail("callback never executed");
        }
        verify(atLeast(1)).that(metaData.functionMetaData);
    }

    [Test]
    public function shouldNotStopIfMetaTypeNotFound():void {
        var callback:Function = function():void {
            callbackCalled = true;
        }
        var callbackCalled:Boolean = false;

        var sample:Object = new Object();
        var metaData:ObjectMetaData = mock(DescribeObjectMetaData);
        assertNotNull(metaData);
        given(metaData.className).willReturn(getQualifiedClassName(Sample1));
        var metadataItem:MetaDataItem = new FunctionMetaDataItem("Mockey", [], "sampleFunc");
        dict["sampleFunc"].push(metadataItem);
        given(metaData.functionMetaData).willReturn(dict);

        var m:MockMetaType = new MockMetaType();
        _worker.addMetaType("NotFound", m);
        _worker.operate(sample, metaData, callback);

        if (!callbackCalled) {
            fail("callback never executed");
        }
        assertTrue(true);
    }

    [Test]
    public function shouldNotStopIfObjectIsNull():void {
        var callback:Function = function():void {
            callbackCalled = true;
        }
        var callbackCalled:Boolean = false;

        var metaData:ObjectMetaData = mock(DescribeObjectMetaData);
        assertNotNull(metaData);
        given(metaData.className).willReturn(getQualifiedClassName(Sample1));
        var metadataItem:MetaDataItem = new FunctionMetaDataItem("Mockey", [], "sampleFunc");
        dict["sampleFunc"].push(metadataItem);
        given(metaData.functionMetaData).willReturn(dict);

        var m:MockMetaType = new MockMetaType();
        _worker.addMetaType("NotFound", m);
        _worker.operate(null, metaData, callback);

        if (!callbackCalled) {
            fail("callback never executed");
        }
        assertTrue(true);
    }

    [Test]
    public function shouldNotStopIfMetaDataIsNull():void {
        var callback:Function = function():void {
            callbackCalled = true;
        }
        var callbackCalled:Boolean = false;

        var sample:Object = new Object();
        var metadataItem:MetaDataItem = new FunctionMetaDataItem("Mockey", [], "sampleFunc");
        dict["sampleFunc"].push(metadataItem);

        var m:MockMetaType = new MockMetaType();
        _worker.addMetaType("NotFound", m);
        _worker.operate(sample, null, callback);

        if (!callbackCalled) {
            fail("callback never executed");
        }
        assertTrue(true);
    }

    [Test]
    public function shouldNotThrowExceptionIfCallbackIsNull():void {
        var sample:Object = new Object();
        try {
            _worker.operate(sample, new DescribeObjectMetaData(), null);
            assertTrue(true);
        } catch (e:TypeError) {
            fail();
        }

    }

    [Test]
    public function shouldNotThrowExceptionIfCallbackAndMetaDataIsNull():void {
        var sample:Object = new Object();
        try {
            _worker.operate(sample, null, null);
            assertTrue(true);
        } catch (e:TypeError) {
            fail();
        }
    }

    [Test]
    public function shouldCallDispose():void {
        var sample:Object = {};
        var metaData:ObjectMetaData = mock(DescribeObjectMetaData);
        assertNotNull(metaData);
        given(metaData.className).willReturn(getQualifiedClassName(Sample1));
        var metadataItem:MetaDataItem = new FunctionMetaDataItem("Mockey", [], "sampleFunc");
        dict["sampleFunc"].push(metadataItem);
        given(metaData.functionMetaData).willReturn(dict);

        _worker.operate(sample, metaData, null);
        _worker.dispose();

        assertNull(_worker.metaTypes);
    }

    [Test(async)]
    [Ignore]
    public function shouldOperateBasedOnPriority():void {
        var sample:Object = {};
        var metaData:ObjectMetaData = mock(DescribeObjectMetaData);
        assertNotNull(metaData);
        given(metaData.className).willReturn(getQualifiedClassName(Sample1));
        var metadataItem:MetaDataItem = new FunctionMetaDataItem("Mockey", [], "sampleFunc");
        dict["sampleFunc"].push(metadataItem);
        metadataItem = new FunctionMetaDataItem("Mockey1", [], "sampleFunc1");
        dict["sampleFunc"].push(metadataItem);
        metadataItem = new FunctionMetaDataItem("Mockey2", [], "sampleFunc2");
        dict["sampleFunc"].push(metadataItem);
        metadataItem = new FunctionMetaDataItem("Mockey3", [], "sampleFunc3");
        dict["sampleFunc"].push(metadataItem);
        given(metaData.functionMetaData).willReturn(dict);
        MockMetaType._callNumber = 0;

        var m:MockMetaType = new MockMetaType();
        m._priority = 20;
        _worker.addMetaType("Mockey", m);
        var m1:MockMetaType = new MockMetaType();
        m1._priority = 10;
        _worker.addMetaType("Mockey1", m1);
        var m2:MockMetaType = new MockMetaType();
        m2._priority = 0;
        _worker.addMetaType("Mockey2", m2);
        var m3:MockMetaType = new MockMetaType();
        m3._priority = -10;
        _worker.addMetaType("Mockey3", m3);

        var metaOperator: MetaOperator = mock(MetaOperator);
        _worker.metaOperator = metaOperator;

        var moduleLoader: ModuleLoader = mock(ModuleLoader);
        given(moduleLoader.getClassByName(getQualifiedClassName(m3))).willReturn(MockMetaType);
//        given(moduleLoader.getClassByName(getQualifiedClassName(m2))).willReturn(MockMetaType);
//        given(moduleLoader.getClassByName(getQualifiedClassName(m1))).willReturn(MockMetaType);
//        given(moduleLoader.getClassByName(getQualifiedClassName(m))).willReturn(MockMetaType);
        _worker.moduleLoader = moduleLoader;

        //todo: issue with multiple givens.  Research and fix
        given(metaOperator.inject(any(), any(), any())).will(useArgument(2).asFunctionAndCall(m2));
//        given(metaOperator.inject).will(useArgument(2).asFunctionAndCall(m2));
//        given(metaOperator.inject).will(useArgument(2).asFunctionAndCall(m2));
//        given(metaOperator.inject).will(useArgument(2).asFunctionAndCall());

        var callback: Function = Async.asyncHandler(this, function(): void {
            assertEquals(1, m3._counter);
            assertEquals(2, m2._counter);
            assertEquals(3, m1._counter);
            assertEquals(4, m._counter);
        }, 300);

        _worker.operate(sample, metaData, callback);

    }

}
}
